#include <vtkActor.h>
#include <vtkCamera.h>
#include <vtkCameraOrientationWidget.h>
#include <vtkNamedColors.h>
#include <vtkNew.h>
#include <vtkOpenGLPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkProperty2D.h>
#include <vtkRenderWindow.h>
#include <vtkRenderer.h>
#include <vtkShaderProgram.h>
#include <vtkSliderRepresentation2D.h>
#include <vtkSliderWidget.h>
#include <vtkSmartPointer.h>
#include <vtkTextProperty.h>
#include <vtkVersion.h>

#include <vtkInteractorStyleTrackballCamera.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkTransform.h>
#include <vtkTransformPolyDataFilter.h>
#include <vtkTriangleFilter.h>
#include <vtkTriangleMeshPointNormals.h>

#include <vtkBYUReader.h>
#include <vtkOBJReader.h>
#include <vtkPLYReader.h>
#include <vtkPolyDataReader.h>
#include <vtkSTLReader.h>
#include <vtkSphereSource.h>
#include <vtkXMLPolyDataReader.h>
#include <vtksys/SystemTools.hxx>

#if VTK_VERSION_NUMBER >= 90020210809ULL
#define VTK_HAS_COW 1
#endif

#if VTK_HAS_COW
#include <vtkCameraOrientationWidget.h>
#endif

#if VTK_VERSION_NUMBER >= 89000000000ULL
#define USE_SHADER_PROPERTIES 1
#include <vtkShaderProperty.h>
#endif

#include <array>
#include <chrono>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <thread>

// Delay time in milliseconds.
// #define DELAY 200
#define DELAY 20

namespace {

struct ShaderProperties
{
  std::array<double, 3> veincolor{0.0, 0.501961, 0.0};
  float veinfreq{7.5};
  int veinlevels{3};
  float warpfreq{1.5};
  float warping{0.5};
  float sharpness{8.0};
} shaderProperties;

std::string dispParameters();

// Commands for the vertex and fragment shaders.
struct ShaderCommand
{
  std::string p1;
  bool p2;
  std::string p3;
  bool p4;
};

ShaderCommand GetVertexShaderReplacement1();

ShaderCommand GetVertexShaderReplacement2();

ShaderCommand GetFragmentShaderReplacement1(std::string const& shaderCode);

ShaderCommand GetFragmentShaderReplacement2();

ShaderCommand GetFragmentShaderReplacement3();

// -----------------------------------------------------------------------
// Update uniform variables in the shader for each render. We do this with
//  a callback for the UpdateShaderEvent.
class ShaderCallback : public vtkCommand
{
public:
  static ShaderCallback* New()
  {
    return new ShaderCallback;
  }

  ShaderCallback()
  {
    this->veincolor = shaderProperties.veincolor;
    this->veinfreq = shaderProperties.veinfreq;
    this->veinlevels = shaderProperties.veinlevels;
    this->warpfreq = shaderProperties.warpfreq;
    this->warping = shaderProperties.warping;
    this->sharpness = shaderProperties.sharpness;
  }

  void Execute(vtkObject*, unsigned long, void* calldata) override
  {
    vtkShaderProgram* program = reinterpret_cast<vtkShaderProgram*>(calldata);
    if (program)
    {
      program->SetUniform3f("veincolor", shaderProperties.veincolor.data());
      program->SetUniformf("veinfreq", shaderProperties.veinfreq);
      program->SetUniformi("veinlevels", shaderProperties.veinlevels);
      program->SetUniformf("warpfreq", shaderProperties.warpfreq);
      program->SetUniformf("warping", shaderProperties.warping);
      program->SetUniformf("sharpness", shaderProperties.sharpness);
      if (HasChanged())
      {
        std::cout << dispParameters();
        Update();
      }
    }
  }

private:
  void Update()
  {
    this->veincolor = shaderProperties.veincolor;
    this->veinfreq = shaderProperties.veinfreq;
    this->veinlevels = shaderProperties.veinlevels;
    this->warpfreq = shaderProperties.warpfreq;
    this->warping = shaderProperties.warping;
    this->sharpness = shaderProperties.sharpness;
  }

  bool HasChanged()
  {
    if (this->veincolor != shaderProperties.veincolor)
    {
      return true;
    }
    if (this->veinfreq != shaderProperties.veinfreq)
    {
      return true;
    }
    if (this->veinlevels != shaderProperties.veinlevels)
    {
      return true;
    }
    if (this->warpfreq != shaderProperties.warpfreq)
    {
      return true;
    }
    if (this->warping != shaderProperties.warping)
    {
      return true;
    }
    if (this->sharpness != shaderProperties.sharpness)
    {
      return true;
    }

    return false;
  }

public:
  std::array<double, 3> veincolor;
  float veinfreq;
  int veinlevels;
  float warpfreq;
  float warping;
  float sharpness;
};

// These callbacks do the actual work.
// Callbacks for the interactions
class SliderCallbackVeinFreq : public vtkCommand
{
public:
  static SliderCallbackVeinFreq* New()
  {
    return new SliderCallbackVeinFreq;
  }

  virtual void Execute(vtkObject* caller, unsigned long, void*)
  {
    vtkSliderWidget* sliderWidget = reinterpret_cast<vtkSliderWidget*>(caller);

    float value = static_cast<vtkSliderRepresentation2D*>(
                      sliderWidget->GetRepresentation())
                      ->GetValue();
    shaderProperties.veinfreq = value;
    std::this_thread::sleep_for(std::chrono::milliseconds(DELAY));
  }

  SliderCallbackVeinFreq() = default;
};

class SliderCallbackVeinLevels : public vtkCommand
{
public:
  static SliderCallbackVeinLevels* New()
  {
    return new SliderCallbackVeinLevels;
  }

  virtual void Execute(vtkObject* caller, unsigned long, void*)
  {
    vtkSliderWidget* sliderWidget = reinterpret_cast<vtkSliderWidget*>(caller);

    int value = static_cast<vtkSliderRepresentation2D*>(
                    sliderWidget->GetRepresentation())
                    ->GetValue();
    static_cast<vtkSliderRepresentation2D*>(sliderWidget->GetRepresentation())
        ->SetValue(value);
    shaderProperties.veinlevels = value;
    std::this_thread::sleep_for(std::chrono::milliseconds(DELAY));
  }

  SliderCallbackVeinLevels() = default;
};

class SliderCallbackWarpFreq : public vtkCommand
{
public:
  static SliderCallbackWarpFreq* New()
  {
    return new SliderCallbackWarpFreq;
  }

  virtual void Execute(vtkObject* caller, unsigned long, void*)
  {
    vtkSliderWidget* sliderWidget = reinterpret_cast<vtkSliderWidget*>(caller);

    float value = static_cast<vtkSliderRepresentation2D*>(
                      sliderWidget->GetRepresentation())
                      ->GetValue();
    shaderProperties.warpfreq = value;
    std::this_thread::sleep_for(std::chrono::milliseconds(DELAY));
  }

  SliderCallbackWarpFreq() : Shader(0)
  {
  }
  ShaderCallback* Shader;
};

class SliderCallbackWarping : public vtkCommand
{
public:
  static SliderCallbackWarping* New()
  {
    return new SliderCallbackWarping;
  }

  virtual void Execute(vtkObject* caller, unsigned long, void*)
  {
    vtkSliderWidget* sliderWidget = reinterpret_cast<vtkSliderWidget*>(caller);

    float value = static_cast<vtkSliderRepresentation2D*>(
                      sliderWidget->GetRepresentation())
                      ->GetValue();
    shaderProperties.warping = value;
    std::this_thread::sleep_for(std::chrono::milliseconds(DELAY));
  }

  SliderCallbackWarping() = default;
};

class SliderCallbackSharpness : public vtkCommand
{
public:
  static SliderCallbackSharpness* New()
  {
    return new SliderCallbackSharpness;
  }

  virtual void Execute(vtkObject* caller, unsigned long, void*)
  {
    vtkSliderWidget* sliderWidget = reinterpret_cast<vtkSliderWidget*>(caller);

    float value = static_cast<vtkSliderRepresentation2D*>(
                      sliderWidget->GetRepresentation())
                      ->GetValue();
    shaderProperties.sharpness = value;
    std::this_thread::sleep_for(std::chrono::milliseconds(DELAY));
  }

  SliderCallbackSharpness() = default;
};

vtkSmartPointer<vtkPolyData> ReadPolyData(const char* fileName);

struct SliderProperties
{
  // Set up the sliders
  double tubeWidth{0.008};
  double sliderLength{0.075};
  double sliderWidth{0.025};
  double endCapLength{0.025};
  double endCapWidth{0.025};
  double titleHeight{0.025};
  double labelHeight{0.020};

  double minimumValue{0.0};
  double maximumValue{1.0};
  double initialValue{0.0};

  std::array<double, 2> p1{0.02, 0.1};
  std::array<double, 2> p2{0.18, 0.1};

  std::string title{""};

  std::string titleColor{"Black"};
  std::string labelColor{"Black"};
  std::string valueColor{"DarkSlateGray"};
  std::string sliderColor{"BurlyWood"};
  std::string selectedColor{"Lime"};
  std::string barColor{"Black"};
  std::string barEndsColor{"Indigo"};
};

vtkNew<vtkSliderWidget> MakeSliderWidget(SliderProperties const& properties,
                                         vtkRenderer* currentRenderer,
                                         vtkRenderWindowInteractor* interactor);

} // namespace

//----------------------------------------------------------------------------
int main(int argc, char* argv[])
{
  if (argc < 2)
  {
    std::cout << "Usage: " << argv[0] << " PerlnNoise.glsl "
              << "[polydataFile] " << "[veincolor(0.0, 0.501961, 0.0)] "
              << "[veinfreq(7.5)] " << "[veinlevels(3)] "
              << "[warpfreq(1.5)] " << "[warping(0.5)] "
              << "[sharpness(8.0)]" << std::endl;
    std::cout << "If the polydata file is not specified,"
                 " a sphere is used instead."
              << std::endl;
    return EXIT_FAILURE;
  }

  vtkNew<vtkNamedColors> colors;

  // Set the background color.
  std::array<unsigned char, 4> bkg{{26, 51, 102, 255}};
  colors->SetColor("BkgColor", bkg.data());

  auto polyData = ReadPolyData(argc > 2 ? argv[2] : "");

  std::ifstream shaderFile(argv[1]);
  std::ostringstream shaderCode;
  shaderCode << shaderFile.rdbuf();

  // This is where we change the defaultshader properties.
  // Defaulting to green.
  shaderProperties.veincolor[0] = colors->GetColor3d("Green").GetData()[0];
  shaderProperties.veincolor[1] = colors->GetColor3d("Green").GetData()[1];
  shaderProperties.veincolor[2] = colors->GetColor3d("Green").GetData()[2];

  // Create a transform to rescale the model.
  double center[3];
  polyData->GetCenter(center);
  double bounds[6];
  polyData->GetBounds(bounds);
  double maxBound =
      std::max(std::max(bounds[1] - bounds[0], bounds[3] - bounds[2]),
               bounds[5] - bounds[4]);

  // Build the pipeline.
  // ren1 is for the slider rendering,
  // ren2 is for the object rendering.
  vtkNew<vtkRenderer> ren1;
  ren1->SetBackground(colors->GetColor3d("Snow").GetData());
  vtkNew<vtkRenderer> ren2;
  vtkNew<vtkOpenGLPolyDataMapper> mapper;
  ren2->SetBackground(colors->GetColor3d("SlateGray").GetData());

  vtkNew<vtkRenderWindow> renderWindow;
  renderWindow->SetSize(800, 480);
  renderWindow->SetWindowName("MarbleShaderDemo");

  // The order here is important.
  // This ensures that the sliders will be in ren1.
  renderWindow->AddRenderer(ren2);
  renderWindow->AddRenderer(ren1);
  ren1->SetViewport(0.0, 0.0, 0.2, 1.0);
  ren2->SetViewport(0.2, 0.0, 1, 1);

  vtkNew<vtkRenderWindowInteractor> interactor;
  interactor->SetRenderWindow(renderWindow);
  vtkNew<vtkInteractorStyleTrackballCamera> style;
  interactor->SetInteractorStyle(style);

  // Rescale the polydata to [-1,1].
  vtkNew<vtkTransform> userTransform;
  userTransform->Translate(-center[0], -center[1], -center[2]);
  userTransform->Scale(1.0 / maxBound, 1.0 / maxBound, 1.0 / maxBound);
  vtkNew<vtkTransformPolyDataFilter> transform;
  transform->SetTransform(userTransform);
  transform->SetInputData(polyData);

  vtkNew<vtkTriangleFilter> triangles;
  triangles->SetInputConnection(transform->GetOutputPort());

  vtkNew<vtkTriangleMeshPointNormals> norms;
  norms->SetInputConnection(triangles->GetOutputPort());

  mapper->SetInputConnection(norms->GetOutputPort());
  mapper->ScalarVisibilityOff();

  vtkNew<vtkActor> actor;

  actor->SetMapper(mapper);
  actor->GetProperty()->SetDiffuse(1.0);
  actor->GetProperty()->SetDiffuseColor(colors->GetColor3d("Wheat").GetData());
  actor->GetProperty()->SetSpecular(.5);
  actor->GetProperty()->SetSpecularPower(50);

  ren2->AddActor(actor);

  // Modify the vertex shader to pass the position of the vertex.
  auto cmd = GetVertexShaderReplacement1();
#if USE_SHADER_PROPERTIES
  vtkShaderProperty* sp = actor->GetShaderProperty();
  sp->AddVertexShaderReplacement(cmd.p1, cmd.p2, cmd.p3, cmd.p4);
#else
  mapper->AddShaderReplacement(vtkShader::Vertex, cmd.p1, cmd.p2, cmd.p3,
                               cmd.p4);
#endif

  cmd = GetVertexShaderReplacement2();
#if USE_SHADER_PROPERTIES
  sp->AddVertexShaderReplacement(cmd.p1, cmd.p2, cmd.p3, cmd.p4);
#else
  mapper->AddShaderReplacement(vtkShader::Vertex, cmd.p1, cmd.p2, cmd.p3,
                               cmd.p4);
#endif

  // Add the code to generate noise.
  // These functions need to be defined outside of main.
  // Use the System::Dec to declare and implement.
  cmd = GetFragmentShaderReplacement1(shaderCode.str());
#if USE_SHADER_PROPERTIES
  sp->AddFragmentShaderReplacement(cmd.p1, cmd.p2, cmd.p3, cmd.p4);
#else
  mapper->AddShaderReplacement(vtkShader::Fragment, // in the fragment shader
                               cmd.p1, cmd.p2, cmd.p3, cmd.p4);
#endif

  // Define varying and uniforms for the fragment shader here.
  cmd = GetFragmentShaderReplacement2();
#if USE_SHADER_PROPERTIES
  sp->AddFragmentShaderReplacement(cmd.p1, cmd.p2, cmd.p3, cmd.p4);
#else
  mapper->AddShaderReplacement(vtkShader::Fragment, // in the fragment shader
                               cmd.p1, cmd.p2, cmd.p3, cmd.p4);
#endif

  cmd = GetFragmentShaderReplacement3();
#if USE_SHADER_PROPERTIES
  sp->AddFragmentShaderReplacement(cmd.p1, cmd.p2, cmd.p3, cmd.p4);
#else
  mapper->AddShaderReplacement(vtkShader::Fragment, // in the fragment shader
                               cmd.p1, cmd.p2, cmd.p3, cmd.p4);
#endif

  std::cout << "------------" << std::endl;
  std::cout << "Input: " << (argc > 2 ? argv[2] : "Generated Sphere")
            << std::endl;
  std::cout << "------------" << std::endl;
  std::cout << dispParameters();
  vtkNew<ShaderCallback> myCallback;
  mapper->AddObserver(vtkCommand::UpdateShaderEvent, myCallback);

  // Setup a slider widget and callback for each varying parameter.

  auto stepSize = 1.0 / 5.0;
  auto posY = 0.1;
  auto posX0 = 0.1;
  auto posX1 = 0.9;

  auto slwP = SliderProperties();

  // Slider values
  slwP.minimumValue = 1.0;
  slwP.initialValue = 7.5;
  slwP.maximumValue = 15.0;
  slwP.title = "Vein Frequency";
  //  Screen coordinates.
  slwP.p1[0] = posX0;
  slwP.p1[1] = posY;
  slwP.p2[0] = posX1;
  slwP.p2[1] = posY;

  auto sliderWidgetVeinFreq = MakeSliderWidget(slwP, ren1, interactor);
  vtkNew<SliderCallbackVeinFreq> callbackVeinFreq;
  sliderWidgetVeinFreq->AddObserver(vtkCommand::InteractionEvent,
                                    callbackVeinFreq);

  posY += stepSize;

  // Slider values
  slwP.minimumValue = 1;
  slwP.initialValue = 3;
  slwP.maximumValue = 5;
  slwP.title = "Vein Levels";
  //  Screen coordinates.
  slwP.p1[0] = posX0;
  slwP.p1[1] = posY;
  slwP.p2[0] = posX1;
  slwP.p2[1] = posY;

  auto sliderWidgetVeinLevels = MakeSliderWidget(slwP, ren1, interactor);
  vtkNew<SliderCallbackVeinLevels> callbackVeinLevels;
  sliderWidgetVeinLevels->AddObserver(vtkCommand::EndInteractionEvent,
                                      callbackVeinLevels);

  posY += stepSize;

  // Slider values
  slwP.minimumValue = 1.0;
  slwP.initialValue = 1.5;
  slwP.maximumValue = 2.0;
  slwP.title = "Warp Frequency";
  //  Screen coordinates.
  slwP.p1[0] = posX0;
  slwP.p1[1] = posY;
  slwP.p2[0] = posX1;
  slwP.p2[1] = posY;

  auto sliderWidgetWarpFreq = MakeSliderWidget(slwP, ren1, interactor);
  vtkNew<SliderCallbackWarpFreq> callbackWarpFreq;
  sliderWidgetWarpFreq->AddObserver(vtkCommand::InteractionEvent,
                                    callbackWarpFreq);

  posY += stepSize;

  // Slider values
  slwP.minimumValue = 0.0;
  slwP.initialValue = 0.5;
  slwP.maximumValue = 1.0;
  slwP.title = "Warping";
  //  Screen coordinates.
  slwP.p1[0] = posX0;
  slwP.p1[1] = posY;
  slwP.p2[0] = posX1;
  slwP.p2[1] = posY;

  auto sliderWidgetWarping = MakeSliderWidget(slwP, ren1, interactor);
  vtkNew<SliderCallbackWarping> callbackWarping;
  sliderWidgetWarping->AddObserver(vtkCommand::InteractionEvent,
                                   callbackWarping);

  posY += stepSize;

  // Slider values
  slwP.minimumValue = 0.01;
  slwP.initialValue = 8.0;
  slwP.maximumValue = 10.0;
  slwP.title = "Sharpness";
  //  Screen coordinates.
  slwP.p1[0] = posX0;
  slwP.p1[1] = posY;
  slwP.p2[0] = posX1;
  slwP.p2[1] = posY;

  auto sliderWidgetSharpness = MakeSliderWidget(slwP, ren1, interactor);
  vtkNew<SliderCallbackSharpness> callbackSharpness;
  sliderWidgetSharpness->AddObserver(vtkCommand::InteractionEvent,
                                     callbackSharpness);

  renderWindow->Render();

#if VTK_HAS_COW
  vtkNew<vtkCameraOrientationWidget> camOrientManipulator;
  camOrientManipulator->SetParentRenderer(ren2);
  // Enable the widget.
  camOrientManipulator->On();
#else
  vtkNew<vtkAxesActor> axes;

  vtkNew<vtkOrientationMarkerWidget> widget;
  double rgba[4]{0.0, 0.0, 0.0, 0.0};
  colors->GetColor("Carrot", rgba);
  widget->SetOutlineColor(rgba[0], rgba[1], rgba[2]);
  widget->SetOrientationMarker(axes);
  widget->SetInteractor(interactor);
  widget->SetViewport(0.0, 0.0, 0.2, 0.2);
  widget->EnabledOn();
  widget->InteractiveOn();
#endif

  ren2->ResetCamera();
  ren2->GetActiveCamera()->Zoom(1.0);
  renderWindow->Render();
  renderWindow->Render();
  interactor->Start();

  transform->GetOutput()->GetBounds(bounds);
  std::cout << "------------" << std::endl;
  std::cout << "Range: " << " x " << bounds[1] - bounds[0] << " y "
            << bounds[3] - bounds[2] << " z " << bounds[5] - bounds[4]
            << std::endl;
  std::cout << "------------" << std::endl;

  return EXIT_SUCCESS;
}

namespace {

std::string dispParameters()
{
  std::stringstream ss;
  ss << "------------" << std::endl;
  ss << "veincolor: " << shaderProperties.veincolor[0] << ", "
     << shaderProperties.veincolor[1] << ", " << shaderProperties.veincolor[2]
     << std::endl;
  ss << "veinfreq: " << shaderProperties.veinfreq << std::endl;
  ss << "veinlevels: " << shaderProperties.veinlevels << std::endl;
  ss << "warpfreq: " << shaderProperties.warpfreq << std::endl;
  ss << "warping: " << shaderProperties.warping << std::endl;
  ss << "sharpness: " << shaderProperties.sharpness << std::endl;
  ss << "------------" << std::endl;
  return ss.str();
}

vtkSmartPointer<vtkPolyData> ReadPolyData(const char* fileName)
{
  vtkSmartPointer<vtkPolyData> polyData;
  std::string extension =
      vtksys::SystemTools::GetFilenameExtension(std::string(fileName));
  if (extension == ".ply")
  {
    vtkNew<vtkPLYReader> reader;
    reader->SetFileName(fileName);
    reader->Update();
    polyData = reader->GetOutput();
  }
  else if (extension == ".vtp")
  {
    vtkNew<vtkXMLPolyDataReader> reader;
    reader->SetFileName(fileName);
    reader->Update();
    polyData = reader->GetOutput();
  }
  else if (extension == ".obj")
  {
    vtkNew<vtkOBJReader> reader;
    reader->SetFileName(fileName);
    reader->Update();
    polyData = reader->GetOutput();
  }
  else if (extension == ".stl")
  {
    vtkNew<vtkSTLReader> reader;
    reader->SetFileName(fileName);
    reader->Update();
    polyData = reader->GetOutput();
  }
  else if (extension == ".vtk")
  {
    vtkNew<vtkPolyDataReader> reader;
    reader->SetFileName(fileName);
    reader->Update();
    polyData = reader->GetOutput();
  }
  else if (extension == ".g")
  {
    vtkNew<vtkBYUReader> reader;
    reader->SetGeometryFileName(fileName);
    reader->Update();
    polyData = reader->GetOutput();
  }
  else
  {
    vtkNew<vtkSphereSource> source;
    source->SetPhiResolution(25);
    source->SetThetaResolution(25);
    source->Update();
    polyData = source->GetOutput();
  }
  return polyData;
}

ShaderCommand GetVertexShaderReplacement1()
{
  ShaderCommand sc;

  sc.p1 = "//VTK::Normal::Dec";  // replace the normal block
  sc.p2 = true;                  // before the standard replacements
  sc.p3 = "//VTK::Normal::Dec\n" // we still want the default
          "  out vec4 myVertexMC;\n";
  sc.p4 = false;                 // only do it once

  return sc;
}

ShaderCommand GetVertexShaderReplacement2()
{
  ShaderCommand sc;

  sc.p1 = "//VTK::Normal::Impl";  // replace the normal block
  sc.p2 = true;                   // before the standard replacements
  sc.p3 = "//VTK::Normal::Impl\n" // we still want the default
          "  myVertexMC = vertexMC;\n";
  sc.p4 = false;                  // only do it once

  return sc;
}

ShaderCommand GetFragmentShaderReplacement1(std::string const& shaderCode)
{
  ShaderCommand sc;

  sc.p1 = "//VTK::System::Dec";
  sc.p2 = false; // before the standard replacements
  sc.p3 = shaderCode;
  sc.p4 = false; // only do it once

  return sc;
}

ShaderCommand GetFragmentShaderReplacement2()
{
  ShaderCommand sc;

  sc.p1 = "//VTK::Normal::Dec";  // replace the normal block
  sc.p2 = false;                 // before the standard replacements
  sc.p3 = "//VTK::Normal::Dec\n" // we still want the default
          "  varying vec4 myVertexMC;\n"
          "  uniform vec3 veincolor = vec3(1.0, 1.0, 1.0);\n"
          "  uniform float veinfreq = 10.0;\n"
          "  uniform int veinlevels = 2;\n"
          "  uniform float warpfreq = 1;\n"
          "  uniform float warping = .5;\n"
          "  uniform float sharpness = 8.0;\n";
  sc.p4 = false;                 // only do it once

  return sc;
}

ShaderCommand GetFragmentShaderReplacement3()
{
  ShaderCommand sc;

  sc.p1 = "//VTK::Light::Impl",  // replace the light block
      sc.p2 = false;             // after the standard replacements
  sc.p3 = "//VTK::Light::Impl\n" // we still want the default calc
          "\n"
          "#define pnoise(x) ((noise(x) + 1.0) / 2.0)\n"
          "#define snoise(x) (2.0 * pnoise(x) - 1.0)\n"
          "  vec3 Ct;\n"
          "  int i;\n"
          "  float turb, freq;\n"
          "  float turbsum;\n"
          "  /* perturb the lookup */\n"
          "  freq = 1.0;\n"
          "  vec4 offset = vec4(0.0,0.0,0.0,0.0);\n"
          "  vec4 noisyPoint;\n"
          "  vec4 myLocalVertexMC = myVertexMC;\n"
          "\n"
          "    for (i = 0;  i < 6;  i += 1) {\n"
          "      noisyPoint[0] = snoise(warpfreq * freq * myLocalVertexMC);\n"
          "      noisyPoint[1] = snoise(warpfreq * freq * myLocalVertexMC);\n"
          "      noisyPoint[2] = snoise(warpfreq * freq * myLocalVertexMC);\n"
          "      noisyPoint[3] = 1.0;\n"
          "      offset += 2.0 * warping * (noisyPoint - 0.5)  / freq;\n"
          "      freq *= 2.0;\n"
          "    }\n"
          "    myLocalVertexMC.x += offset.x;\n"
          "    myLocalVertexMC.y += offset.y;\n"
          "    myLocalVertexMC.z += offset.z;\n"
          "\n"
          "    /* Now calculate the veining function for the lookup area */\n"
          "    turbsum = 0.0;  freq = 1.0;\n"
          "    myLocalVertexMC *= veinfreq;\n"
          "    for (i = 0;  i < veinlevels;  i += 1) {\n"
          "      turb = abs (snoise (myLocalVertexMC));\n"
          "      turb = pow (smoothstep (0.8, 1.0, 1.0 - turb), sharpness) / "
          "freq;\n"
          "      turbsum += (1.0-turbsum) * turb;\n"
          "      freq *= 1.5;\n"
          "      myLocalVertexMC *= 1.5;\n"
          "    }\n"
          "\n"
          "    Ct = mix (diffuseColor, veincolor, turbsum);\n"
          "\n"
          "  fragOutput0.rgb = opacity * (ambientColor + Ct + specular);\n"
          "  fragOutput0.a = opacity;\n";
  sc.p4 = false;                 // only do it once

  return sc;
}

vtkNew<vtkSliderWidget> MakeSliderWidget(SliderProperties const& properties,
                                         vtkRenderer* currentRenderer,
                                         vtkRenderWindowInteractor* interactor)
{
  vtkNew<vtkNamedColors> colors;
  vtkNew<vtkSliderRepresentation2D> slider;

  slider->SetMinimumValue(properties.minimumValue);
  slider->SetMaximumValue(properties.maximumValue);
  slider->SetValue(properties.initialValue);
  slider->SetTitleText(properties.title.c_str());

  slider->GetPoint1Coordinate()->SetCoordinateSystemToNormalizedViewport();
  slider->GetPoint1Coordinate()->SetValue(properties.p1[0], properties.p1[1]);
  slider->GetPoint2Coordinate()->SetCoordinateSystemToNormalizedViewport();
  slider->GetPoint2Coordinate()->SetValue(properties.p2[0], properties.p2[1]);

  slider->SetTubeWidth(properties.tubeWidth);
  slider->SetSliderLength(properties.sliderLength);
  slider->SetSliderWidth(properties.sliderWidth);
  slider->SetEndCapLength(properties.endCapLength);
  slider->SetEndCapWidth(properties.endCapWidth);
  slider->SetTitleHeight(properties.titleHeight);
  slider->SetLabelHeight(properties.labelHeight);

  // Set the color properties
  // Change the color of the title.
  slider->GetTitleProperty()->SetColor(
      colors->GetColor3d(properties.titleColor).GetData());
  // Change the color of the label.
  slider->GetLabelProperty()->SetColor(
      colors->GetColor3d(properties.labelColor).GetData());
  // Change the color of the bar.
  slider->GetTubeProperty()->SetColor(
      colors->GetColor3d(properties.barColor).GetData());
  // Change the color of the ends of the bar.
  slider->GetCapProperty()->SetColor(
      colors->GetColor3d(properties.barEndsColor).GetData());
  // Change the color of the knob that slides.
  slider->GetSliderProperty()->SetColor(
      colors->GetColor3d(properties.sliderColor).GetData());
  // Change the color of the knob when the mouse is held on it.
  slider->GetSelectedProperty()->SetColor(
      colors->GetColor3d(properties.selectedColor).GetData());
  // Change the color of the text displaying the value.
  slider->GetLabelProperty()->SetColor(
      colors->GetColor3d(properties.valueColor).GetData());

  vtkNew<vtkSliderWidget> sliderWidget;
  sliderWidget->SetCurrentRenderer(currentRenderer);
  sliderWidget->SetInteractor(interactor);
  sliderWidget->SetRepresentation(slider);
  sliderWidget->SetAnimationModeToAnimate();
  sliderWidget->SetNumberOfAnimationSteps(10);
  sliderWidget->EnabledOn();

  return sliderWidget;
}

} // namespace
